package zdream.control.world.buff;

import java.util.function.Predicate;

import zdream.control.foe.mm.MegaMan;
import zdream.control.world.MFoe;
import zdream.control.world.MFoeBuff;
import zdream.control.world.MFoeConstants;
import zdream.control.world.MHookType;
import zdream.control.world.MHooks;
import zdream.control.world.Ticker;

import static zdream.control.world.MFoeConstants.*;

/**
 * @author Zdream
 * @since 0.0.1
 * @date 2022-06-03
 */
public class HealthModule extends MFoeBuff {
	public static final String CATEGORY = "health";

	public static final String STATE_IMMUNE = MFoeConstants.TAG_IMMUNE;

	public HealthModule(MFoe foe) {
		super(foe);
	}

	@Override
	public void init() {
		onHook(MHookType.TICK)
				.withPriority(500)
				.handle(this::tick)
				.buildAndSave();
		onHook(MHookType.RECEIVE_DAMAGE)
				.withPriority(0)
				.handle(this::dropHP)
				.buildAndSave();
		onHook(MHookType.RECEIVE_DAMAGE)
				.withPriority(1000)
				.handle(this::afterReceiveDamage)
				.buildAndSave();
		onHook(MHookType.RECOVER)
				.withPriority(0)
				.handle(this::recoverHP)
				.buildAndSave();
	}

	public void tick() {
		final Ticker ticker = foe.world.ticker;
		boolean destroyed = checkHp() || handleRemain(ticker);
		if (destroyed) {
			return;
		}
		handleImmune(ticker);
	}

	@Override
	public String getCategory() {
		return CATEGORY;
	}

	/* **********
	 *    HP    *
	 ********** */
	public int hp, hpMax = 0;

	public long immuneDuration = 50; // 0.05s
	public long immuneRemain;
	public boolean dieWhenHpIsZero = false;
	public byte armorLevel = ARMOR_LEVEL_NONE;
	public byte normalArmorLevel = ARMOR_LEVEL_NONE;
	public byte immuneArmorLevel = ARMOR_LEVEL_IMMUNE;

	private void handleImmune(Ticker ticker) {
		if (immuneRemain <= 0) {
			return;
		}

		immuneRemain -= ticker.lDuration;
		if (immuneRemain <= 0) {
			immuneRemain = 0;
			clearImmune();
		}
	}

	/**
	 * 强制结束无敌时间
	 */
	public void clearImmune() {
		immuneRemain = 0;
		armorLevel = normalArmorLevel;
		removeTag(STATE_IMMUNE);
	}

	public void dropHP(MHooks.MHookContext rawContext) {
		if (rawContext.aborted) {
			return;
		}

		MHooks.DamageContext context = rawContext.as();
		if (context.level <= armorLevel) {
			context.absorb();
			return;
		}

		context.withHpFrom(hp);
		hp = Math.max(hp - context.damage, 0);
		context.withHpTo(hp).accept();

		if (foe.is(MegaMan.NAME)) {
			System.out.printf("%d HP:%d -> %d%n", foe.world.ticker.worldTime, context.hpFrom, context.hpTo);
		}
	}

	public void afterReceiveDamage(MHooks.MHookContext rawContext) {
		MHooks.DamageContext context = rawContext.as();
		if (!context.accepted) {
			return;
		}
		immuneRemain = immuneDuration;
		armorLevel = immuneArmorLevel;
		addTag(STATE_IMMUNE);

		if (hp <= 0) {
			context.markTargetDie();
			foe.destroy(MFoe.DestroyCause.ZERO_HP);
		}
	}

	public void recoverHP(MHooks.MHookContext rawContext) {
		if (rawContext.aborted) {
			return;
		}

		MHooks.RecoverContext context = rawContext.as();
		context.withHpFrom(hp);
		hp = Math.min(hp + context.value, hpMax);
		context.withHpTo(hp);
	}

	/* **********
	 * 出界销毁 *
	 ********** */
	/**
	 * 角色销毁的保护时间, 单位: ms, 配置项
	 */
	public long outsideTrialPeriod = 1000;
	/**
	 * 角色判断时长, 在房间外超过多少时间, 触发 foe 的自动销毁, 单位: ms, 配置项.
	 * -1 表示不自动销毁
	 */
	public long outsideThreshold = 100;
	/**
	 * 角色在房间外的时长, 单位: ms, 状态项
	 */
	public long outsideDuration;
	/**
	 * 最大生命时长. 超过该时长, foe 自动销毁. 单位: ms
	 */
	public long maxAge;
	public Predicate<MFoe> outsideChecker = null;

	/**
	 * @return
	 *   如果确定自毁了, 就返回 true
	 */
	private boolean handleRemain(Ticker ticker) {
		long age = foe.age;
		if (age > outsideTrialPeriod && outsideThreshold >= 0) {
			if (isOutside()) {
				// 如果跑到房间外
				outsideDuration += ticker.lDuration;
			} else {
				outsideDuration = 0;
			}

			if (outsideDuration > outsideThreshold) {
				foe.destroy(MFoe.DestroyCause.OUTSIDE);
				return true;
			}
		}
		if (maxAge > 0 && age > maxAge) {
			foe.destroy();
			return true;
		}
		return false;
	}

	/**
	 * @return
	 *   如果确定自毁了, 就返回 true
	 */
	private boolean checkHp() {
		if (dieWhenHpIsZero && hp <= 0) {
			foe.destroy(MFoe.DestroyCause.ZERO_HP);
			return true;
		}
		return false;
	}

	/**
	 * 判断是否在房间外.
	 */
	public boolean isOutside() {
		foe.flush();
		if (outsideChecker != null) {
			return outsideChecker.test(foe);
		}
		return foe.world.isOutsideRoom(foe);
	}

	// 下面是备选的 outsideChecker

	static boolean roomOutsideCheck(MFoe foe) {
		return foe.world.isOutsideCamera(foe);
	}

	/* **********
	 *   工厂   *
	 ********** */

	public static HealthModuleFactory builder(MFoe foe) {
		return new HealthModuleFactory(foe);
	}

	public static class HealthModuleFactory {
		HealthModule module;
		public HealthModuleFactory(MFoe foe) {
			module = new HealthModule(foe);
		}
		public HealthModuleFactory hpMax(int hpMax) {
			module.hpMax = hpMax * HP_TOKEN;
			module.hp = module.hpMax;
			module.dieWhenHpIsZero = true;
			return this;
		}
		public HealthModuleFactory maxAgeInMS(long age) {
			module.maxAge = age;
			return this;
		}
		public HealthModuleFactory immuneDuration(long durationInMS) {
			module.immuneDuration = durationInMS;
			return this;
		}
		public HealthModuleFactory outsideDestroyThreshold(long durationInMS) {
			module.outsideThreshold = durationInMS;
			return this;
		}
		public HealthModuleFactory setRoomOutsideChecker() {
			module.outsideChecker = HealthModule::roomOutsideCheck;
			return this;
		}
		public HealthModuleFactory doNotDestroyWhenOutside() {
			module.outsideThreshold = -1;
			return this;
		}
		public HealthModuleFactory armorLevel(byte level) {
			module.normalArmorLevel = level;
			module.armorLevel = level;
			return this;
		}
		public HealthModuleFactory armorLevelWhenImmune(byte level) {
			module.immuneArmorLevel = level;
			return this;
		}
		public HealthModule create() {
			module.foe.addBuff(module);
			return module;
		}
	}
}
